#ifndef _foobar2000_sdk_service_h_included_
#define _foobar2000_sdk_service_h_included_

typedef const void* service_class_ref;

PFC_DECLARE_EXCEPTION(exception_service_not_found,pfc::exception,"Service not found");
PFC_DECLARE_EXCEPTION(exception_service_extension_not_found,pfc::exception,"Service extension not found");
PFC_DECLARE_EXCEPTION(exception_service_duplicated,pfc::exception,"Service duplicated");

#ifdef _MSC_VER
#define FOOGUIDDECL __declspec(selectany)
#else
#define FOOGUIDDECL
#endif


#define DECLARE_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME = {A,S,D,{F,G,H,J,K,L,Z,X}};
#define DECLARE_CLASS_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME::class_guid = {A,S,D,{F,G,H,J,K,L,Z,X}};

//! Special hack to ensure errors when someone tries to ->service_add_ref()/->service_release() on a service_ptr_t
template<typename T>
class service_obscure_refcounting : public T
{
private:
    int service_add_ref() throw();
    int service_release() throw();
};

//! Converts a service interface pointer to a pointer that obscures service counter functionality.
template<typename T>
static inline service_obscure_refcounting<T>* service_obscure_refcounting_cast(T * p_source) throw()
{
    return static_cast<service_obscure_refcounting<T>*>(p_source);
}

//Must be templated instead of taking service_base* because of multiple inheritance issues.
template<typename T>
static void service_release_safe(T * p_ptr) throw()
{
    if (p_ptr != NULL) PFC_ASSERT_NO_EXCEPTION( p_ptr->service_release() );
}

//Must be templated instead of taking service_base* because of multiple inheritance issues.
template<typename T>
static void service_add_ref_safe(T * p_ptr) throw()
{
    if (p_ptr != NULL) PFC_ASSERT_NO_EXCEPTION( p_ptr->service_add_ref() );
}

class service_base;

//! Autopointer class to be used with all services. Manages reference counter calls behind-the-scenes.
template<typename T>
class service_ptr_t
{
private:
    typedef service_ptr_t<T> t_self;
public:
    inline service_ptr_t() throw()
            : m_ptr(NULL)
    {}
    inline service_ptr_t(T* p_ptr) throw()
            : m_ptr(NULL)
    {
        copy(p_ptr);
    }
    inline service_ptr_t(const t_self & p_source) throw()
            : m_ptr(NULL)
    {
        copy(p_source);
    }

    template<typename t_source>
    inline service_ptr_t(t_source * p_ptr) throw()
            : m_ptr(NULL)
    {
        copy(p_ptr);
    }

    template<typename t_source>
    inline service_ptr_t(const service_ptr_t<t_source> & p_source) throw()
            : m_ptr(NULL)
    {
        copy(p_source);
    }

    inline ~service_ptr_t() throw()
    {
        service_release_safe(m_ptr);
    }

    template<typename t_source>
    void copy(t_source * p_ptr) throw()
    {
        service_add_ref_safe(p_ptr);
        service_release_safe(m_ptr);
        m_ptr = pfc::safe_ptr_cast<T>(p_ptr);
    }

    template<typename t_source>
    inline void copy(const service_ptr_t<t_source> & p_source) throw()
    {
        copy(p_source.get_ptr());
    }


    inline const t_self & operator=(const t_self & p_source) throw()
    {
        copy(p_source);
        return *this;
    }
    inline const t_self & operator=(T * p_ptr) throw()
    {
        copy(p_ptr);
        return *this;
    }

    template<typename t_source> inline t_self & operator=(const service_ptr_t<t_source> & p_source) throw()
    {
        copy(p_source);
        return *this;
    }
    template<typename t_source> inline t_self & operator=(t_source * p_ptr) throw()
    {
        copy(p_ptr);
        return *this;
    }

    inline void release() throw()
    {
        service_release_safe(m_ptr);
        m_ptr = NULL;
    }


    inline service_obscure_refcounting<T>* operator->() const throw()
    {
        PFC_ASSERT(m_ptr != NULL);
        return service_obscure_refcounting_cast(m_ptr);
    }

    inline T* get_ptr() const throw()
    {
        return m_ptr;
    }

    inline bool is_valid() const throw()
    {
        return m_ptr != NULL;
    }
    inline bool is_empty() const throw()
    {
        return m_ptr == NULL;
    }

    inline bool operator==(const t_self & p_item) const throw()
    {
        return m_ptr == p_item.get_ptr();
    }
    inline bool operator!=(const t_self & p_item) const throw()
    {
        return m_ptr != p_item.get_ptr();
    }
    inline bool operator>(const t_self & p_item) const throw()
    {
        return m_ptr > p_item.get_ptr();
    }
    inline bool operator<(const t_self & p_item) const throw()
    {
        return m_ptr < p_item.get_ptr();
    }

    template<typename t_other>
    inline t_self & operator<<(service_ptr_t<t_other> & p_source) throw()
    {
        attach(p_source.detach());
        return *this;
    }
    template<typename t_other>
    inline t_self & operator>>(service_ptr_t<t_other> & p_dest) throw()
    {
        p_dest.attach(detach());
        return *this;
    }


    inline T* __unsafe_duplicate() const throw()  //should not be used ! temporary !
    {
        service_add_ref_safe(m_ptr);
        return m_ptr;
    }

    inline T* detach() throw()
    {
        return pfc::replace_null_t(m_ptr);
    }

    template<typename t_source>
    inline void attach(t_source * p_ptr) throw()
    {
        service_release_safe(m_ptr);
        m_ptr = pfc::safe_ptr_cast<T>(p_ptr);
    }

    T & operator*() const throw()
    {
        return *m_ptr;
    }

    service_ptr_t<service_base> & _as_base_ptr()
    {
        PFC_ASSERT( _as_base_ptr_check() );
        return *reinterpret_cast<service_ptr_t<service_base>*>(this);
    }
    static bool _as_base_ptr_check()
    {
        return static_cast<service_base*>((T*)NULL) == reinterpret_cast<service_base*>((T*)NULL);
    }
private:
    T* m_ptr;
};

namespace pfc
{
    template<typename T>
    class traits_t<service_ptr_t<T> > : public traits_default
    {
    public:
        enum { realloc_safe = true, constructor_may_fail = false};
    };
}


template<typename T, template<typename> class t_alloc = pfc::alloc_fast>
class service_list_t : public pfc::list_t<service_ptr_t<T>, t_alloc >
{
};

//! Helper macro for use when defining a service class. Generates standard features of a service, without ability to register using service_factory / enumerate using service_enum_t. \n
//! This is used for declaring services that are meant to be instantiated by means other than service_enum_t (or non-entrypoint services), or extensions of services (including extension of entrypoint services).	\n
//! Sample non-entrypoint declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, service_base); };	\n
//! Sample extension declaration: class myclass : public myotherclass {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, myotherclass); };	\n
//! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes.
#define FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,PARENTCLASS) \
	public:	\
		typedef THISCLASS t_interface;	\
		typedef PARENTCLASS t_interface_parent;	\
			\
		static const GUID class_guid;	\
			\
		virtual bool service_query(service_ptr_t<service_base> & p_out,const GUID & p_guid) {	\
			if (p_guid == class_guid) {p_out = this; return true;}	\
			else return PARENTCLASS::service_query(p_out,p_guid);	\
		}	\
		typedef service_ptr_t<t_interface> ptr;	\
	protected:	\
		THISCLASS() {}	\
		~THISCLASS() {}	\
	private:	\
		const THISCLASS & operator=(const THISCLASS &) {throw pfc::exception_not_implemented();}	\
		THISCLASS(const THISCLASS &) {throw pfc::exception_not_implemented();}	\
	private:	\
		void __private__service_declaration_selftest() {	\
			pfc::assert_same_type<PARENTCLASS,PARENTCLASS::t_interface>(); /*parentclass must be an interface*/	\
			__validate_service_class_helper<THISCLASS>(); /*service_base must be reachable by walking t_interface_parent*/	\
			pfc::safe_cast<service_base*>(this); /*this class must derive from service_base, directly or indirectly, and be implictly castable to it*/ \
		}

//! Helper macro for use when defining an entrypoint service class. Generates standard features of a service, including ability to register using service_factory and enumerate using service_enum.	\n
//! Sample declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myclass); };	\n
//! Note that entrypoint service classes must directly derive from service_base, and not from another service class.
//! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes.
#define FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS)	\
	public:	\
		typedef THISCLASS t_interface_entrypoint;	\
	FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,service_base)


#define FB2K_DECLARE_SERVICE_BEGIN(THISCLASS,BASECLASS) \
	class NOVTABLE THISCLASS : public BASECLASS	{	\
		FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,BASECLASS);	\
	public:

#define FB2K_DECLARE_SERVICE_END() \
	};

#define FB2K_DECLARE_SERVICE_ENTRYPOINT_BEGIN(THISCLASS) \
	class NOVTABLE THISCLASS : public service_base {	\
		FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS)	\
	public:


//! Base class for all service classes.\n
//! Provides interfaces for reference counter and querying for different interfaces supported by the object.\n
class NOVTABLE service_base
{
public:
    //! Decrements reference count; deletes the object if reference count reaches zero. This is normally not called directly but managed by service_ptr_t<> template.
    //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable.
    virtual int service_release() throw() = 0;
    //! Increments reference count. This is normally not called directly but managed by service_ptr_t<> template.
    //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable.
    virtual int service_add_ref() throw() = 0;
    //! Queries whether the object supports specific interface and retrieves a pointer to that interface. This is normally not called directly but managed by service_query_t<> function template.
    //! Typical implementation checks the parameter against GUIDs of interfaces supported by this object, if the GUID is one of supported interfaces, p_out is set to service_base pointer that can be static_cast<>'ed to queried interface and the method returns true; otherwise the method returns false.
    virtual bool service_query(service_ptr_t<service_base> & p_out,const GUID & p_guid)
    {
        return false;
    }

    //! Queries whether the object supports specific interface and retrieves a pointer to that interface.
    //! @param p_out Receives pointer to queried interface on success.
    //! returns true on success, false on failure (interface not supported by the object).
    template<class T>
    bool service_query_t(service_ptr_t<T> & p_out)
    {
        pfc::assert_same_type<T,T::t_interface>();
        return service_query( *reinterpret_cast<service_ptr_t<service_base>*>(&p_out),T::class_guid);
    }

    typedef service_base t_interface;

protected:
    service_base() {}
    ~service_base() {}
private:
    service_base(const service_base&)
    {
        throw pfc::exception_not_implemented();
    }
    const service_base & operator=(const service_base&)
    {
        throw pfc::exception_not_implemented();
    }
};

typedef service_ptr_t<service_base> service_ptr;

template<typename T>
static void __validate_service_class_helper()
{
    __validate_service_class_helper<T::t_interface_parent>();
}

template<>
static void __validate_service_class_helper<service_base>() {}


#include "service_impl.h"

class NOVTABLE service_factory_base
{
protected:
    inline service_factory_base(const GUID & p_guid) : m_guid(p_guid)
    {
        PFC_ASSERT(!core_api::are_services_available());
        __internal__next=__internal__list;
        __internal__list=this;
    }
    inline ~service_factory_base()
    {
        PFC_ASSERT(!core_api::are_services_available());
    }
public:
    inline const GUID & get_class_guid() const
    {
        return m_guid;
    }

    static service_class_ref enum_find_class(const GUID & p_guid);
    static bool enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index);
    static t_size enum_get_count(service_class_ref p_class);

    inline static bool is_service_present(const GUID & g)
    {
        return enum_get_count(enum_find_class(g))>0;
    }

    //! Throws std::bad_alloc or another exception on failure.
    virtual void instance_create(service_ptr_t<service_base> & p_out) = 0;

    //! FOR INTERNAL USE ONLY
    static service_factory_base *__internal__list;
    //! FOR INTERNAL USE ONLY
    service_factory_base * __internal__next;
private:
    const GUID & m_guid;
};


template<typename B>
class service_factory_base_t : public service_factory_base
{
public:
    service_factory_base_t() : service_factory_base(B::class_guid)
    {
        pfc::assert_same_type<B,B::t_interface_entrypoint>();
    }

};


template<typename T> static void _validate_service_ptr(service_ptr_t<T> const & ptr)
{
    PFC_ASSERT( ptr.is_valid() );
    service_ptr_t<T> test;
    PFC_ASSERT( ptr->service_query_t(test) );
}

#ifdef _DEBUG
#define FB2K_ASSERT_VALID_SERVICE_PTR(ptr) _validate_service_ptr(ptr)
#else
#define FB2K_ASSERT_VALID_SERVICE_PTR(ptr)
#endif

template<class T> static bool service_enum_create_t(service_ptr_t<T> & p_out,t_size p_index)
{
    pfc::assert_same_type<T,T::t_interface_entrypoint>();
    service_ptr_t<service_base> ptr;
    if (service_factory_base::enum_create(ptr,service_factory_base::enum_find_class(T::class_guid),p_index))
    {
        p_out = static_cast<T*>(ptr.get_ptr());
        return true;
    }
    else
    {
        p_out.release();
        return false;
    }
}

template<typename T> static service_class_ref _service_find_class()
{
    pfc::assert_same_type<T,T::t_interface_entrypoint>();
    return service_factory_base::enum_find_class(T::class_guid);
}

template<typename what>
static bool _service_instantiate_helper(service_ptr_t<what> & out, service_class_ref servClass, t_size index)
{
    /*if (out._as_base_ptr_check()) {
    	const bool state = service_factory_base::enum_create(out._as_base_ptr(), servClass, index);
    	if (state) { FB2K_ASSERT_VALID_SERVICE_PTR(out); }
    	return state;
    } else */
    {
        service_ptr temp;
        const bool state = service_factory_base::enum_create(temp, servClass, index);
        if (state)
        {
            out.attach( static_cast<what*>( temp.detach() ) );
            FB2K_ASSERT_VALID_SERVICE_PTR( out );
        }
        return state;
    }
}

template<typename T> class service_class_helper_t
{
public:
    service_class_helper_t() : m_class(service_factory_base::enum_find_class(T::class_guid))
    {
        pfc::assert_same_type<T,T::t_interface_entrypoint>();
    }
    t_size get_count() const
    {
        return service_factory_base::enum_get_count(m_class);
    }

    bool create(service_ptr_t<T> & p_out,t_size p_index) const
    {
        return _service_instantiate_helper(p_out, m_class, p_index);
    }

    service_ptr_t<T> create(t_size p_index) const
    {
        service_ptr_t<T> temp;
        if (!create(temp,p_index)) throw pfc::exception_bug_check_v2();
        return temp;
    }
    service_class_ref get_class() const
    {
        return m_class;
    }
private:
    service_class_ref m_class;
};

void _standard_api_create_internal(service_ptr & out, const GUID & classID);

template<typename T> static void standard_api_create_t(service_ptr_t<T> & p_out)
{
    if (pfc::is_same_type<T,T::t_interface_entrypoint>::value)
    {
        _standard_api_create_internal(p_out._as_base_ptr(), T::class_guid);
        FB2K_ASSERT_VALID_SERVICE_PTR(p_out);
    }
    else
    {
        service_ptr_t<T::t_interface_entrypoint> temp;
        standard_api_create_t(temp);
        if (!temp->service_query_t(p_out)) throw exception_service_extension_not_found();
    }
}

template<typename T> static service_ptr_t<T> standard_api_create_t()
{
    service_ptr_t<T> temp;
    standard_api_create_t(temp);
    return temp;
}

template<typename T>
static bool static_api_test_t()
{
    typedef T::t_interface_entrypoint EP;
    service_class_helper_t<EP> helper;
    if (helper.get_count() != 1) return false;
    if (!pfc::is_same_type<T,EP>::value)
    {
        service_ptr_t<T> t;
        if (!helper.create(0)->service_query_t(t)) return false;
    }
    return true;
}

#define FB2K_API_AVAILABLE(API) static_api_test_t<API>()

//! Helper template used to easily access core services. \n
//! Usage: static_api_ptr_t<myclass> api; api->dosomething();
//! Can be used at any point of code, WITH EXCEPTION of static objects that are initialized during the DLL loading process before the service system is initialized; such as static static_api_ptr_t objects or having static_api_ptr_t instances as members of statically created objects.
//! Throws exception_service_not_found if service could not be reached (which can be ignored for core APIs that are always present unless there is some kind of bug in the code).
template<typename t_interface>
class static_api_ptr_t
{
public:
    static_api_ptr_t()
    {
        standard_api_create_t(m_ptr);
    }
    service_obscure_refcounting<t_interface>* operator->() const
    {
        return service_obscure_refcounting_cast(m_ptr.get_ptr());
    }
    t_interface* get_ptr() const
    {
        return m_ptr.get_ptr();
    }
private:
    service_ptr_t<t_interface> m_ptr;
};

//! Helper; simulates array with instance of each available implementation of given service class.
template<typename T> class service_instance_array_t
{
public:
    typedef service_ptr_t<T> t_ptr;
    service_instance_array_t()
    {
        service_class_helper_t<T> helper;
        const t_size count = helper.get_count();
        m_data.set_size(count);
        for (t_size n=0;n<count;n++) m_data[n] = helper.create(n);
    }

    t_size get_size() const
    {
        return m_data.get_size();
    }
    const t_ptr & operator[](t_size p_index) const
    {
        return m_data[p_index];
    }

    //nonconst version to allow sorting/bsearching; do not abuse
    t_ptr & operator[](t_size p_index)
    {
        return m_data[p_index];
    }
private:
    pfc::array_t<t_ptr> m_data;
};

template<typename t_interface>
class service_enum_t
{
public:
    service_enum_t() : m_index(0)
    {
        pfc::assert_same_type<t_interface,typename t_interface::t_interface_entrypoint>();
    }
    void reset()
    {
        m_index = 0;
    }

    template<typename t_query>
    bool first(service_ptr_t<t_query> & p_out)
    {
        reset();
        return next(p_out);
    }

    template<typename t_query>
    bool next(service_ptr_t<t_query> & p_out)
    {
        pfc::assert_same_type<typename t_query::t_interface_entrypoint,t_interface>();
        if (pfc::is_same_type<t_query,t_interface>::value)
        {
            return __next(reinterpret_cast<service_ptr_t<t_interface>&>(p_out));
        }
        else
        {
            service_ptr_t<t_interface> temp;
            while (__next(temp))
            {
                if (temp->service_query_t(p_out)) return true;
            }
            return false;
        }
    }

private:
    bool __next(service_ptr_t<t_interface> & p_out)
    {
        return m_helper.create(p_out,m_index++);
    }
    unsigned m_index;
    service_class_helper_t<t_interface> m_helper;
};

template<typename T>
class service_factory_t : public service_factory_base_t<typename T::t_interface_entrypoint>
{
public:
    void instance_create(service_ptr_t<service_base> & p_out)
    {
        p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(  new service_impl_t<T>  )));
    }
};

template<typename T>
class service_factory_single_t : public service_factory_base_t<typename T::t_interface_entrypoint>
{
    service_impl_single_t<T> g_instance;
public:
    TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_factory_single_t,g_instance)

    void instance_create(service_ptr_t<service_base> & p_out)
    {
        p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(&g_instance)));
    }

    inline T& get_static_instance()
    {
        return g_instance;
    }
};

template<typename T>
class service_factory_single_ref_t : public service_factory_base_t<typename T::t_interface_entrypoint>
{
private:
    T & instance;
public:
    service_factory_single_ref_t(T& param) : instance(param) {}

    void instance_create(service_ptr_t<service_base> & p_out)
    {
        p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(&instance)));
    }

    inline T& get_static_instance()
    {
        return instance;
    }
};


template<typename T>
class service_factory_single_transparent_t : public service_factory_base_t<typename T::t_interface_entrypoint>, public service_impl_single_t<T>
{
public:
    TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_factory_single_transparent_t,service_impl_single_t<T>)

    void instance_create(service_ptr_t<service_base> & p_out)
    {
        p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(this)));
    }

    inline T& get_static_instance()
    {
        return *(T*)this;
    }
};



template<typename what>
static bool service_by_guid_fallback(service_ptr_t<what> & out, const GUID & id)
{
    service_enum_t<what> e;
    service_ptr_t<what> ptr;
    while (e.next(ptr))
    {
        if (ptr->get_guid() == id)
        {
            out = ptr;
            return true;
        }
    }
    return false;
}

template<typename what>
class service_by_guid_data
{
public:
    service_by_guid_data() : m_servClass(), m_inited() {}

    bool ready() const
    {
        return m_inited;
    }

    void initialize()
    {
        if (m_inited) return;
        pfc::assert_same_type< what, typename what::t_interface_entrypoint >();
        m_servClass = service_factory_base::enum_find_class(what::class_guid);
        const t_size servCount = service_factory_base::enum_get_count(m_servClass);
        for (t_size walk = 0; walk < servCount; ++walk)
        {
            service_ptr_t<what> temp;
            if (_service_instantiate_helper(temp, m_servClass, walk))
            {
                m_order.set(temp->get_guid(), walk);
            }
        }
        m_inited = true;
    }

    bool create(service_ptr_t<what> & out, const GUID & id) const
    {
        PFC_ASSERT(m_inited);
        t_size index;
        if (!m_order.query(id,index)) return false;
        return _service_instantiate_helper(out, m_servClass, index);
    }
    service_ptr_t<what> create(const GUID & id) const
    {
        service_ptr_t<what> temp;
        if (!crete(temp,id)) throw exception_service_not_found();
        return temp;
    }

private:
    volatile bool m_inited;
    pfc::map_t<GUID,t_size> m_order;
    service_class_ref m_servClass;
};

template<typename what>
class _service_by_guid_data_container
{
public:
    static service_by_guid_data<what> data;
};
template<typename what> service_by_guid_data<what> _service_by_guid_data_container<what>::data;


template<typename what>
static void service_by_guid_init()
{
    service_by_guid_data<what> & data = _service_by_guid_data_container<what>::data;
    data.initialize();
}

template<typename what>
static bool service_by_guid(service_ptr_t<what> & out, const GUID & id)
{
    pfc::assert_same_type< what, typename what::t_interface_entrypoint >();
    service_by_guid_data<what> & data = _service_by_guid_data_container<what>::data;
    if (data.ready())
    {
        //fall-thru
    }
    else if (core_api::is_main_thread())
    {
        data.initialize();
    }
    else
    {
#ifdef _DEBUG
        uDebugLog() << "Warning: service_by_guid() used in non-main thread without initialization, using fallback";
#endif
        return service_by_guid_fallback(out,id);
    }
    return data.create(out,id);
}

template<typename what>
static service_ptr_t<what> service_by_guid(const GUID & id)
{
    service_ptr_t<what> temp;
    if (!service_by_guid(temp,id)) throw exception_service_not_found();
    return temp;
}

#endif //_foobar2000_sdk_service_h_included_
